package cn.com.sparkle.paxos;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import cn.com.sparkle.paxos.event.ConfigureEvent;
import cn.com.sparkle.paxos.event.EventsManager;

public class Configuration {
	private HashSet<ConfigNode> senators = new HashSet<ConfigNode>();
	private HashSet<ConfigNode> nodes = new HashSet<ConfigNode>();
	private HashSet<ConfigNode> followers = new HashSet<ConfigNode>();
	private int port;
	private int clientPort;
	private String stableStorage;
	private String selfAddress = "";
	private long version;
	private EventsManager eventsManager;
	private String filePath;
	private long responseDelay;
	private int transportTcpNum;
	private String netLayer;

	public Configuration(String filePath, EventsManager eventsManager)
			throws ParserConfigurationException, SAXException, IOException {
		this.eventsManager = eventsManager;
		this.filePath = filePath;

		checkNewFile();

		Properties followerProp = new Properties();
		File follower = new File(filePath + "/followers.prop");
		followerProp.load(new InputStreamReader(new FileInputStream(follower)));
		for (String address : followerProp.getProperty("followers").split(",")) {
			if (address.trim().length() != 0) {
				followers.add(ConfigNode.parseNode(address));
			}
		}

		Properties senatorProp = new Properties();
		File senator = new File(filePath + "/senators.prop");
		senatorProp.load(new InputStreamReader(new FileInputStream(senator)));
		String sAddress = senatorProp.getProperty("senators");
		version = Long.parseLong(senatorProp.getProperty("version"));
		for (String address : sAddress.split(",")) {
			if (address.trim().length() != 0) {
				address = address.trim();
				if (followers.contains(address)) {
					throw new ConfigurationError(
							"One senator must be not a follower!(Address:"
									+ address + ")");
				}
				senators.add(ConfigNode.parseNode(address));
			}
		}

		if (senators.size() == 0) {
			throw new ConfigurationError("one senator at least are needed!");
		}
		nodes.addAll(followers);
		nodes.addAll(senators);

		Properties confProp = new Properties();
		File conf = new File(filePath + "/config.prop");
		confProp.load(new InputStreamReader(new FileInputStream(conf)));

		this.stableStorage = confProp.getProperty("stable-storage");
		this.port = Integer.parseInt(confProp.getProperty("listen-port"));
		this.clientPort = Integer.parseInt(confProp
				.getProperty("listen-port-client"));
		this.responseDelay = Long.parseLong(confProp
				.getProperty("max-response-delay"));
		this.transportTcpNum = Integer.parseInt(confProp
				.getProperty("transport-tcp-num"));
		this.netLayer = confProp.getProperty("net-layer");

		File file = new File(this.stableStorage);
		if (file.exists() && !file.isDirectory()) {
			throw new ConfigurationError("stable-storage is not directory!");
		} else if (!file.exists()) {
			file.mkdirs();
		}
		// format file path
		this.stableStorage = file.getCanonicalPath();

	}

	public synchronized void addSenator(String address, long version)
			throws ConfigurationException {
		if (version <= this.version) {
			return;
		}
		ConfigNode senator = ConfigNode.parseNode(address);
		if (followers.contains(senator)) {
			throw new ConfigurationException("this candidate of senator "
					+ senator + " is a follower!");
		}
		senators.add(senator);
		this.version = version;
		saveSenator();
		ConfigureEvent.doSenatorsChangeEvent(eventsManager, senators, version);
	}

	public synchronized void removeSenator(String address, long version)
			throws ConfigurationException {
		if (version <= this.version) {
			return;
		}
		ConfigNode senator = ConfigNode.parseNode(address);
		if (senators.contains(senator) && senators.size() == 1) {
			throw new ConfigurationException(
					"The system must need one senator at least!");
		} else {
			senators.remove(senator);
			this.version = version;
			saveSenator();
			ConfigureEvent.doSenatorsChangeEvent(eventsManager, senators,
					version);
		}
	}

	public synchronized void addFollower(String address)
			throws ConfigurationException {
		ConfigNode follower = ConfigNode.parseNode(address);
		if (senators.contains(follower)) {
			throw new ConfigurationException("this candidate of follower "
					+ follower + " is a senator!");
		}
		if (!followers.contains(follower)) {
			followers.add(follower);
			saveFollower();
			ConfigureEvent.doFollowersChangeEvent(eventsManager, followers);
		}
	}

	public synchronized void removeFollower(String address) {
		ConfigNode follower = ConfigNode.parseNode(address);
		if (!followers.contains(follower)) {
			if (followers.remove(follower)) {
				saveFollower();
				ConfigureEvent.doFollowersChangeEvent(eventsManager, followers);
			}
		}
	}

	private void checkNewFile() {
		File sf = new File(filePath + "/senators.prop");
		File sfn = new File(filePath + "/senators.prop.new");
		if (!sf.exists()) {
			if (!sfn.exists()) {
				throw new ConfigurationError(
						"old and new senator configuration is not existed!");
			} else {
				sfn.renameTo(sf);
			}
		}
		sfn.delete();

		File ff = new File(filePath + "/followers.prop");
		File ffn = new File(filePath + "/followers.prop.new");

		if (!ff.exists()) {
			if (!ffn.exists()) {
				throw new ConfigurationError(
						"old and new follower configuration is not existed!");
			} else {
				ffn.renameTo(sf);
			}
		}
		ffn.delete();
	}

	private void saveSenator() {
		Properties p = new Properties();
		p.setProperty("version", String.valueOf(version));
		StringBuffer sb = new StringBuffer();
		for (ConfigNode senator : senators) {
			sb.append(senator.toString()).append(",");
		}
		p.setProperty("senators", sb.toString());
		File f = new File(filePath + "/senators.prop.new");
		if (!f.exists()) {
			f.mkdir();
		}
		OutputStream os = null;
		try {
			os = new FileOutputStream(f);
			p.store(os, "");
			os.close();
			File ff = new File(filePath + "/senators.prop");
			ff.delete();
			f.renameTo(ff);
		} catch (IOException e) {
			throw new ConfigurationError(
					"senator configuration write file failed.");
		} finally {
			if (os != null) {
				try {
					os.close();
				} catch (Throwable e) {
				}
			}
		}
	}

	private void saveFollower() {
		Properties p = new Properties();
		StringBuffer sb = new StringBuffer();
		for (ConfigNode senator : followers) {
			sb.append(senator).append(",");
		}
		p.setProperty("followers", sb.toString());
		File f = new File(filePath + "/followers.prop.new");
		if (!f.exists()) {
			f.mkdir();
		}
		OutputStream os = null;
		try {
			os = new FileOutputStream(f);
			p.store(os, "");
			os.close();
			File ff = new File(filePath + "/followers.prop");
			ff.delete();
			f.renameTo(ff);
		} catch (IOException e) {
			throw new ConfigurationError(
					"followers configuration write file failed.");
		} finally {
			if (os != null) {
				try {
					os.close();
				} catch (Throwable e) {
				}
			}
		}
	}

	public int getTransportTcpNum() {
		return transportTcpNum;
	}

	public int getClientPort() {
		return clientPort;
	}

	public long getResponseDelay() {
		return responseDelay;
	}

	public Set<ConfigNode> getNodes() {
		return nodes;
	}

	public String getStableStorage() {
		return stableStorage;
	}

	public String getSelfAddress() {
		return selfAddress;
	}

	public void setSelfAddress(String address) {
		this.selfAddress = address;
	}

	public HashSet<ConfigNode> getSenators() {
		return senators;
	}

	public void setSenators(HashSet<ConfigNode> senators) {
		this.senators = senators;
	}

	public HashSet<ConfigNode> getFollowers() {
		return followers;
	}

	public void setFollowers(HashSet<ConfigNode> followers) {
		this.followers = followers;
	}

	public void setNodes(HashSet<ConfigNode> nodes) {
		this.nodes = nodes;
	}

	public void setStableStorage(String stableStorage) {
		this.stableStorage = stableStorage;
	}

	public long getVersion() {
		return version;
	}

	public int getPort() {
		return port;
	}

	public String getNetLayer() {
		return netLayer;
	}

	public String getFilePath() {
		return filePath;
	}
	
}
